package com.jeaven.utils;

import com.jeaven.classfile.constantpool.ConstantPool;
import com.jeaven.classfile.constantpool.ConstantPoolInfo;
import com.jeaven.classfile.constantpool.content.*;
import com.jeaven.rtda.Frame;
import com.jeaven.rtda.Slot;
import com.jeaven.rtda.heap.KMethod;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Utils {
    public static byte[] readNBytes(DataInputStream is, int length) throws IOException {
        byte[] bytes = new byte[length];
        for(int i = 0; i < length; i++) {
            bytes[i] = is.readByte();
        }
        return bytes;
    }

    public static String getClassName(ConstantPool cp, int classIndex) {
        int nameIndex = ((ClassCp)cp.infos[classIndex-1]).nameIndex;
        return ((Utf8)cp.infos[nameIndex-1]).getString();
    }

    public static String getString(ConstantPool cp, int attributeNameIndex) {
        return  ((Utf8)cp.infos[attributeNameIndex-1]).getString();
    }

    // 根据方法的描述符返回参数类型
    public static List<String> parseMethodDescriptor(String descriptor) {
        String args = descriptor.substring(descriptor.indexOf("("), descriptor.indexOf(")")+1);
        List<String> result = new ArrayList<>();
        List<Character> baseType = Arrays.asList('B', 'C', 'D', 'F','I', 'J', 'S', 'Z');
        for(int index = 0; index < args.length(); index++) {
            // ;
            if(args.charAt(index) == ';') {
                continue;
            }
            // 基本类型变量 B C D F I J S Z
            if(baseType.contains(args.charAt(index))) {
                result.add(String.valueOf(args.charAt(index)));
                continue;
            }
            // 引用类型 L
            if(args.charAt(index) == 'L') {
                int index_temp = index;
                List<Character> temp = new ArrayList<>();
                temp.add('L');
                index ++;
                while(args.charAt(index) != ';') {
                    index ++;
                }
                String temp_res = args.substring(index_temp, index);
                result.add(temp_res);
                continue;
            }
            // 数组 [[...
            if(args.charAt(index) == '[') {
                int index_temp = index;
                index++;
                while(args.charAt(index) == '[') {
                    index++;
                }
                while(args.charAt(index) != ';') {
                    index ++;
                }
                String temp_res = args.substring(index_temp, index);
                result.add(temp_res);
                continue;
            }
        }
        return result;
    }

//    // 生成 Native Method 的 key 值
//    public static String genNativeMethodKey(KMethod method) {
//        return genNativeMethodKey(method.clazz.name, method.name, method.descriptor);
//    }
//
//    public static String genNativeMethodKey(String clazz, String name, String descriptor) {
//        return clazz + "_" + name + "_" + descriptor;
//    }

    // 根据参数来生成 native 方法的索引
    public static String generateNativeMethodKey(KMethod kmethod) {
        String className = kmethod.clazz.name;
        String methodName = kmethod.name;
        String methodDescriptor = kmethod.descriptor;
        return className + "_" + methodName + "_" + methodDescriptor;
    }

    public static Object pop(Frame frame, String descriptor) {
        switch (descriptor) {
            case "I":
            case "B":
            case "C":
            case "S":
            case "Z":
                return frame.operandStack.popInt();
            case "F":
                return frame.operandStack.popFloat();
            case "J":
                return frame.operandStack.popLong();
            case "D":
                return frame.operandStack.popDouble();
            default:
                return frame.operandStack.popRef();
        }
    }

    public static void push(Frame frame, String descriptor, Object obj) {
        switch (descriptor) {
            case "I":
            case "B":
            case "C":
            case "S":
            case "Z":
                frame.operandStack.pushInt(((Integer) obj), Slot.INT);
                break;
            case "J":
                frame.operandStack.pushLong(((Long) obj));
                break;
            case "F":
                frame.operandStack.pushFloat(((Float) obj), Slot.FLOAT);
                break;
            case "D":
                frame.operandStack.pushDouble(((Double) obj));
                break;
            default:
                frame.operandStack.pushRef(obj);
                break;
        }
    }

    public static int setLocals(Frame frame, int idx, String descriptor, Object val) {
        int ret = 1;
        switch (descriptor) {
            case "I":
            case "B":
            case "C":
            case "S":
            case "Z":
                frame.localVars.setInt(idx, (Integer) val);
                break;
            case "J":
                frame.localVars.setLong(idx, (Long) val);
                ret++;
                break;
            case "F":
                frame.localVars.setFloat(idx, (Float) val);
                break;
            case "D":
                frame.localVars.setDouble(idx, (Double) val);
                ret++;
                break;
            default:
                frame.localVars.setRef(idx, val);
                break;
        }
        return ret;
    }

    public static String getClassNameByMethodDefIdx(ConstantPool constantPool, int mdIdx) {
        ConstantPoolInfo methodInfo = constantPool.infos[mdIdx - 1];
        MethodRef methodDef = (MethodRef) methodInfo;
        return getClassName(constantPool, methodDef.classIndex);
    }

    public static String getMethodTypeByMethodDefIdx(ConstantPool cp, int mdIdx) {
        ConstantPoolInfo methodInfo = cp.infos[mdIdx - 1];
        MethodRef methodDef = (MethodRef) methodInfo;
        return getTypeByNameAndTypeIdx(cp, methodDef.nameAndTypeIndex);
    }

    public static String getTypeByNameAndTypeIdx(ConstantPool cp, int natIdx) {
        int idx = ((NameAndType) cp.infos[natIdx - 1]).descriptionIndex;
        return getString(cp, idx);
    }

    public static String getNameByNameAndTypeIdx(ConstantPool cp, int natIdx) {
        int nameIndex = ((NameAndType) cp.infos[natIdx - 1]).nameIndex;
        return getString(cp, nameIndex);
    }

    public static String getMethodNameByIMethodDefIdx(ConstantPool cp, int mdIdx) {
        ConstantPoolInfo methodInfo = cp.infos[mdIdx - 1];
        InterfaceMethodRef methodDef = (InterfaceMethodRef) methodInfo;
        return getNameByNameAndTypeIdx(cp, methodDef.nameAndTypeIndex);
    }


}
